import * as Box2D from "box2d.ts";
export class myBox2D {
  canvas: HTMLCanvasElement;
  ctx: CanvasRenderingContext2D;

  world: Box2D.b2World | null = null;
  floor: Rect | null = null;
  rect: Rect | null = null;
  circular: Circular | null = null;
  polygon: Polygon | null = null;
  complex: Complex | null = null;
  joint: RevoluteJoint | null = null;
  special: Circular | null = null;

  animateFrame: number = 0;
  test: number = 0;

  constructor(option: { canvas: HTMLCanvasElement; ctx: CanvasRenderingContext2D }) {
    this.canvas = option.canvas;
    this.ctx = option.ctx;
  }

  init() {
    this.world = new Box2D.b2World(new Box2D.b2Vec2(0, 9.8));
    this.world.m_allowSleep = true;
    console.log("物理世界创建完成")
  }

  animate = () => {
    let offsetLeft = 0
    this.world?.Step(1 / 60, 8, 3);
    let node = this.world?.GetBodyList();
    while (node) {
      let b = node;
      node = node.GetNext();
      let fixtrue = b?.GetFixtureList();
      let userdata = b.GetUserData();
      
      if (userdata.name == "polygon") {
        this.renderPolygon(this.ctx, b!.GetPosition(), b!.GetAngle(), fixtrue!,userdata,offsetLeft);
      }
      if (userdata.name == "circular") {
        this.renderCircular(this.ctx, b!.GetPosition(), b!.GetAngle(), fixtrue!);
      }
      if (userdata.name == "complex") {
        while (fixtrue) {
          let f = fixtrue;
          fixtrue = fixtrue.GetNext();
          this.renderComplex(this.ctx, b!.GetPosition(), b!.GetAngle(), f!);
        }
      }
    }

    this.test++;
    if (this.test > 20) {
      console.log(node)
      cancelAnimationFrame(this.animateFrame);
    }
  };

  renderComplex(ctx: CanvasRenderingContext2D, position: Box2D.b2Vec2, angle: number, f: Box2D.b2Fixture) {
    ctx.save();
    ctx.beginPath();
    ctx.translate(position.x, position.y);
    ctx.rotate(angle);
    if (f.GetShape().m_radius) {
      let radius: number = f.GetShape().m_radius;
      ctx.arc(0, 0, radius, 0, 2 * Math.PI);
    }
    if (f.GetShape().m_vertices) {
      let points: Box2D.b2Vec2[] = f.GetShape().m_vertices;
      points.forEach((point, index) => {
        if (index == 0) {
          ctx.moveTo(point.x, point.y);
        } else {
          ctx.lineTo(point.x, point.y);
        }
      });
    }
    ctx.closePath();
    ctx.stroke();
    ctx.restore();
  }

  renderCircular(ctx: CanvasRenderingContext2D, position: Box2D.b2Vec2, angle: number, f: Box2D.b2Fixture) {
    ctx.save();
    ctx.beginPath();
    ctx.translate(position.x, position.y);
    ctx.rotate(angle);
    let radius: number = f.GetShape().m_radius;
    ctx.arc(0, 0, radius, 0, 2 * Math.PI);
    ctx.closePath();
    ctx.stroke();
    ctx.restore();
  }

  renderPolygon(ctx: CanvasRenderingContext2D, position: Box2D.b2Vec2, angle: number, f: Box2D.b2Fixture,userdata:{name: string,sprite:HTMLImageElement},offsetLeft:number) {
    ctx.save();
    ctx.beginPath();
    ctx.translate(position.x-offsetLeft, position.y);
    ctx.rotate(angle);
    let points: Box2D.b2Vec2[] = f.GetShape().m_vertices;
    points.forEach((point, index) => {
      if (index == 0) {
        ctx.moveTo(point.x, point.y);
      } else {
        ctx.lineTo(point.x, point.y);
      }
    });
    ctx.closePath();
    ctx.clip()
    if (userdata.sprite) {
      ctx.save()
      ctx.transform(
        1,
        0,
        0,
        1,
        -Math.abs(points[0].x),
        -Math.abs(points[0].y)
        )
      ctx.drawImage(userdata.sprite,0,0,userdata.sprite.width,userdata.sprite.height,0,0,Math.abs(points[0].x)*2,Math.abs(points[0].y)*2)
      ctx.restore()
    }
    ctx.strokeStyle = "#ff0000"
    ctx.stroke();
    ctx.restore();
  }
}

export class RevoluteJoint {
  body1: Rect | Circular | Polygon | Complex;
  body2: Rect | Circular | Polygon | Complex;
  constructor(option: { body1: Rect | Circular | Polygon | Complex; body2: Rect | Circular | Polygon | Complex; center: Box2D.b2Vec2; world: Box2D.b2World }) {
    this.body1 = option.body1;
    this.body2 = option.body2;

    let jointDef = new Box2D.b2RevoluteJointDef();
    let jointCenter = option.center;
    jointDef.Initialize(this.body1.body, this.body2.body, jointCenter);
    option.world.CreateJoint(jointDef);
  }
}

export class Complex {
  body: Box2D.b2Body;
  bodyDef: Box2D.b2BodyDef;
  fixture: Box2D.b2FixtureDef;

  constructor(option: { world: Box2D.b2World; position: { x: number; y: number }; density: number; friction: number; restitution: number; shapes: any[]; type: number; userdata: { name: string } }) {
    this.bodyDef = new Box2D.b2BodyDef();
    this.bodyDef.type = option.type;
    this.bodyDef.position.x = option.position.x;
    this.bodyDef.position.y = option.position.y;
    this.body = option.world.CreateBody(this.bodyDef);
    this.body.SetUserData(option.userdata);

    this.fixture = new Box2D.b2FixtureDef();
    this.fixture.density = option.density;
    this.fixture.friction = option.friction;
    this.fixture.restitution = option.restitution;

    option.shapes.forEach((shape) => {
      this.fixture.shape = shape;
      this.body.CreateFixture(this.fixture);
    });
  }
}

export class Polygon {
  body: Box2D.b2Body;
  bodyDef: Box2D.b2BodyDef;
  fixture: Box2D.b2FixtureDef;

  constructor(option: { world: Box2D.b2World; position: { x: number; y: number }; density: number; friction: number; restitution: number; points: { x: number; y: number }[]; type: number; userdata: { name: string } }) {
    this.bodyDef = new Box2D.b2BodyDef();
    this.bodyDef.type = option.type;
    this.bodyDef.position.x = option.position.x;
    this.bodyDef.position.y = option.position.y;
    this.body = option.world.CreateBody(this.bodyDef);
    this.body.SetUserData(option.userdata);

    this.fixture = new Box2D.b2FixtureDef();
    this.fixture.density = option.density;
    this.fixture.friction = option.friction;
    this.fixture.restitution = option.restitution;
    this.fixture.shape = new Box2D.b2PolygonShape().SetAsArray(option.points);
    this.body.CreateFixture(this.fixture);
  }
}

export class Circular {
  body: Box2D.b2Body;
  bodyDef: Box2D.b2BodyDef;
  fixture: Box2D.b2FixtureDef;
  drawDiy(): void {}

  constructor(option: { world: Box2D.b2World; position: { x: number; y: number }; density: number; friction: number; restitution: number; radius: number; type: number; userdata: any }) {
    this.bodyDef = new Box2D.b2BodyDef();
    this.bodyDef.type = option.type;
    this.bodyDef.position.x = option.position.x;
    this.bodyDef.position.y = option.position.y;
    this.body = option.world.CreateBody(this.bodyDef);
    this.body.SetUserData(option.userdata);

    this.fixture = new Box2D.b2FixtureDef();
    this.fixture.density = option.density;
    this.fixture.friction = option.friction;
    this.fixture.restitution = option.restitution;
    this.fixture.shape = new Box2D.b2CircleShape(option.radius);
    this.body.CreateFixture(this.fixture);
  }
}

export class Rect {
  body: Box2D.b2Body;
  bodyDef: Box2D.b2BodyDef;
  fixture: Box2D.b2FixtureDef;

  constructor(option: { world: Box2D.b2World; position: { x: number; y: number }; density: number; friction: number; restitution: number; size: { x: number; y: number }; type: number; userdata: { name: string,sprite:HTMLImageElement } }) {

    this.bodyDef = new Box2D.b2BodyDef();
    this.bodyDef.type = option.type;
    this.bodyDef.position.x = option.position.x;
    this.bodyDef.position.y = option.position.y;
    this.body = option.world.CreateBody(this.bodyDef);
    this.body.SetUserData(option.userdata);

    this.fixture = new Box2D.b2FixtureDef();
    this.fixture.density = option.density;
    this.fixture.friction = option.friction;
    this.fixture.restitution = option.restitution;
    this.fixture.shape = new Box2D.b2PolygonShape().SetAsBox(option.size.x, option.size.y);
    this.body.CreateFixture(this.fixture);
  }
}
